package net.intellect.collectPro.businessLogic;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.intellect.force.ClientSalesforce;
import net.intellect.force.Constants;
import net.intellect.force.Utilities;

import com.sforce.soap.enterprise.ID;
import com.sforce.soap.enterprise.InvalidIdFault;
import com.sforce.soap.enterprise.LoginFault;
import com.sforce.soap.enterprise.UnexpectedErrorFault;
import com.sforce.soap.enterprise.sobject.Agent__c;
import com.sforce.soap.enterprise.sobject.Agent_category_product__c;
import com.sforce.soap.enterprise.sobject.CP_Activity__c;
import com.sforce.soap.enterprise.sobject.CP_Enterprise_Account__c;
import com.sforce.soap.enterprise.sobject.Category_Product__c;
import com.sforce.soap.enterprise.sobject.Cycle__c;
import com.sforce.soap.enterprise.sobject.Holiday__c;
import com.sforce.soap.enterprise.sobject.Past_Due__c;
import com.sforce.soap.enterprise.sobject.SObject;
import com.sforce.soap.enterprise.sobject.Shift__c;

public class CycleAssignment {
	
	protected static Logger logger = Logger.getLogger("assignment");
	// agents map - organized by product-category
	Map<String,HashMap<String,List<Agent__c>>> agentCatProdMap = new HashMap<String,HashMap<String,List<Agent__c>>>();
	// agent category-product configuration map - organized by agent id, product and category
	Map<String, HashMap<String, HashMap<String, Agent_category_product__c>>> categoryProductsMap = new 
		HashMap<String, HashMap<String, HashMap<String, Agent_category_product__c>>>();
	// agents ids list
	List<String> agentIdsArray = new ArrayList<String>();
	// Map which contains current assigned activities for each agent - increased when a new past due is assigned
	Map<String, List<CP_Activity__c>> agentLoadMap = new HashMap<String, List<CP_Activity__c>>();
	// List of new activities (ones must be inserted)
	List<SObject> newActivities = new ArrayList<SObject>();
	// client ids list
	List<String> clientIdsList = new ArrayList<String>();
	// Past dues list organized by amount and category
	List<Past_Due__c> pastDueSortedList = new ArrayList<Past_Due__c>();
	// PastDues assigned map
	Map<String, Past_Due__c> pastDuesAssignedMap = new HashMap<String, Past_Due__c>();
	List<SObject> pastDuesAssignedList = new ArrayList<SObject>();
	// Map<ShiftId, Shift>
	Map<String, Shift__c> shiftMap = new HashMap<String, Shift__c>();
	// Map<clientId, List<Past_due>>
	HashMap<String, ArrayList<Past_Due__c>> clientPastDuesMap = new HashMap<String, ArrayList<Past_Due__c>>();
	// Map<Product, Map<Category, Category_Product__c>>
	HashMap<String, HashMap<String, Category_Product__c>> categoryProductMap = new HashMap<String, HashMap<String, Category_Product__c>>();
	
	public CycleAssignment() { 
		logger.getLevel();
		
	}
	
	/**
	 * Assigns a cycle (payments and pay dues) to the company agents
	 * @param cycle
	 * @return true when successful, false when error found
	 * @throws Exception 
	 * @throws LoginFault 
	 * @throws InvalidIdFault 
	 * @throws UnexpectedErrorFault 
	 * @throws RemoteException 
	 */
	public boolean assignCycle(String enterpriseId, String cycleId) 
		throws RemoteException, UnexpectedErrorFault, InvalidIdFault, LoginFault, Exception {
		boolean allRight = false;

		if (null != cycleId) {
			Cycle__c currentCycle = null;
			CP_Enterprise_Account__c enterprise = null;
			// Map<ProductId,Map<categoryId,int>>
			HashMap<String, HashMap<String,Integer>> productCategoriesIndexMap = new HashMap<String, HashMap<String,Integer>>();
			// Holiday map
			Map<String, HashMap<Integer, Holiday__c>> holidayMap = null;

			// 0. GETS ENTERPRISE 
			try {
				enterprise = (CP_Enterprise_Account__c)ClientSalesforce.queryLimitOne(
					"Select Id, Name, Company__r.Result_Code_for_Closing_an_Activity__c, " +
					"Company__r.Code_for_Closing_an_Activity__c, Company__c from CP_Enterprise_Account__c "+
					"where Enterprise_Account_Code__c = '" + enterpriseId + "' LIMIT 1");
			} catch(Exception e) { 
				logger.log(Level.INFO, "COLLECT - Enterprise Account can not be found - End of process");
				throw new Exception("Enterprise Account can not be found - End of process");
			}
			
			if (null != enterprise) {
				// 1. GETS CURRENT CYCLE
				try {
					currentCycle = (Cycle__c)ClientSalesforce.queryLimitOne(
						"Select Id, CPEE_Closed_Cycle__c, Period__c, Enterprise_Account__c, Company__c, "+
						"CPEE_Cycle_Start_Date__c, CPEE_Cycle_End_Date__c from Cycle__c "+
						"where External_Identifier__c = '" + cycleId + "' " + 
						"and Enterprise_Account__c = '" + enterprise.getId().getID() + "' LIMIT 1");
				} catch(Exception e) { 
					logger.log(Level.INFO, "COLLECT - Current Cycle not found - End of process");
					throw new Exception("Current Cycle not found - End of process");
				}
	
				// 2. CLOSES LAST PERIOD
				closeLastPeriod(currentCycle);
	
				// 3. LOOKS FOR AGENTS AND CATEGORY-PRODUCT CONFIGURATION
				constructAgentsMaps(currentCycle);
				if (agentCatProdMap.size() > 0) {
					int lastIndex = 0;
					boolean returning = false;
	
					// 4. LOOKS FOR AGENTS LOAD
					calculateAgentCurrentLoad(currentCycle, enterprise);
	
					// 5. LOOKS FOR CLIENT'S PAST DUES
					createPastDueSortedStructures(enterprise, currentCycle);
	
					// GETS HOLIDAYS MAP
					holidayMap = Utilities.getHolidayMap(enterprise.getId().getID());
					getCategoryProductConfiguration(enterprise.getId().getID());
					getAgentShifts(agentIdsArray, enterprise.getCompany__c().getID());
	
					// ****************************************************** SECOND PART ********************************************************
					// ************************************************** ASIGNACION - RANKS *****************************************************
					logger.log(Level.INFO, "COLLECT - ASSIGNMENT BY RANKS");
					for (int i=0; null!=pastDueSortedList && i<pastDueSortedList.size(); i++) {
						try { 
							Past_Due__c pastDue = pastDueSortedList.get(i);
							logger.log(Level.INFO, "COLLECT - Trying to assing past due: " + pastDue.getName() + " / total consolidated: " + pastDue.getTotal_Balance_Consolidate__c() );
	
							String productId = pastDue.getCP_Product__c().toString();
							String categoryId = pastDue.getCategory__c().toString();
							List<Agent__c> agentList = new ArrayList<Agent__c>();
	
							// finds agents which match product and category
							if (null != agentCatProdMap )
								if (agentCatProdMap.containsKey(productId))
									if (agentCatProdMap.get(productId).containsKey(categoryId))
										agentList = agentCatProdMap.get(productId).get(categoryId);
	
							if (agentList.size() > 0) {
								// Saves last used index from agent list - in order to use the very next index for the following past due
								if (!productCategoriesIndexMap.containsKey(productId)) 
									productCategoriesIndexMap.put(productId, new HashMap<String,Integer>());
								if (!productCategoriesIndexMap.get(productId).containsKey(categoryId)) 
									productCategoriesIndexMap.get(productId).put(categoryId, new Integer(0));
	
								// Gets the last index used for this category/product
								lastIndex = productCategoriesIndexMap.get(productId).get(categoryId);
	
								// If last index was reached last time, set program to use the index from end to start (...3, 2, 1)
								if (lastIndex == agentList.size() && agentList.size() > 0) { returning = true; lastIndex = lastIndex-1;}
								// If first index where reached last time, set program to use the index form start to end (0, 1, 2...)
								else if (lastIndex-1 < 0) { returning = false; lastIndex = 0; }
								int currentIndex = lastIndex;
	
								// iterates over agent list for the product and the category of the past due
								while (currentIndex < agentList.size() && currentIndex >= 0) {
									Agent__c agent = agentList.get(currentIndex);
									logger.log(Level.INFO, "COLLECT - Trying to assign to agent: " + agent.getName());
									int load = (agentLoadMap.containsKey(agent.getId().getID())) ? (agentLoadMap.get(agent.getId().getID()).size()) : 0;
									Agent_category_product__c agentCategoryProduct = 
										categoryProductsMap.get(agent.getId().getID()).get(productId).get(categoryId);
	
									// verifies load
									if (load < agent.getCPEE_Maximum_Cycle__c()) {
										// verifies ranks
										if (pastDue.getTotal_Balance_Consolidate__c() >= agentCategoryProduct.getMinimum_Amount_to_Allocate__c() &&
											pastDue.getTotal_Balance_Consolidate__c() <= agentCategoryProduct.getMaximum_Amount_to_Allocate__c()) {
	
											// Calculate activity scheduling date
											Date availableDate = getAvailableDate(agent, pastDue, currentCycle, holidayMap);
											if (null != availableDate) {
	
												// 1. ASSIGN NEW ACTIVITY
												CP_Activity__c activity = getNewActivity(pastDue, agent, availableDate); 
												newActivities.add(activity);
	
												// 2. ASSIGN PAST DUE - UPDATES AGENT
												assignPastDue(pastDue, activity, agent.getId(), productId, categoryId);
	
												// registers last used agent index
												productCategoriesIndexMap.get(productId).put(categoryId, new Integer(returning ? currentIndex-1 : currentIndex+1));
													
												logger.log(Level.INFO, "COLLECT - ASSIGNED PAST DUE " + pastDue.getName() + " / to agent '" + agent.getName() + "' " +
														pastDue.getCP_Product__r().getName() + " / " + pastDue.getCategory__r().getName());
											} else logger.log(Level.INFO, "COLLECT - No date available to assign this past due to agent " + agent.getName());
											break;
	
										} else logger.log(Level.INFO, "COLLECT - ranks not reached: " + agentCategoryProduct.getMinimum_Amount_to_Allocate__c() +
										  		" / " + agentCategoryProduct.getMaximum_Amount_to_Allocate__c() + " for agent " +
										  		agent.getName());
									} else logger.log(Level.INFO, "COLLECT - Agent cycle load reached " + agent.getCPEE_Maximum_Cycle__c());
	
									// increases or decreases the index, depending on the direction going forward or going backward 
									currentIndex = returning ? currentIndex-1 : currentIndex+1;
								} // END WHILE
	
							} else logger.log(Level.INFO, "COLLECT - No agent found for " + pastDue.getName() + " / " +
									pastDue.getCP_Product__r().getName() + "/ " + pastDue.getCategory__r().getName());
						} catch(Exception e) { logger.log(Level.SEVERE, "COLLECT - ERROR WHILE PROCESING PAST DUE INDEX: " + i + " / " + e); }
	 				} // END ASSIGNATION BY RANKS
	
					
					// ****************************************************** SECOND PART ********************************************************
					// ********************************************** ASSIGNMENT BY AVAILABILITY *************************************************
					logger.log(Level.INFO, "COLLECT - ASSIGNMENT BY AVAILABILITY - ASSIGNES ALL THE PAST DUE FROM A SINGLE CLIENT TO A SINGLE AGENT");
					Map<String,Past_Due__c> pastDuesForSupervisors = new HashMap<String,Past_Due__c>();
	
					// Iterates over past dues list
					for (int i=0; i<pastDueSortedList.size(); i++) {
						Past_Due__c pastDue = pastDueSortedList.get(i);
						String productId = pastDue.getCP_Product__c().toString();
						String categoryId = pastDue.getCategory__c().toString();
						List<Agent__c> agentList = new ArrayList<Agent__c>();
						boolean agentFound = false;
						List<Past_Due__c> pastDuesToAssign = new ArrayList<Past_Due__c>();
	
						try {
							// Verifies this past due was not assigned on ranks assignment
							if (!pastDuesAssignedMap.containsKey(pastDue.getId().getID()) && !pastDuesForSupervisors.containsKey(pastDue.getId().getID())) {
								logger.log(Level.INFO, "Trying to assing past dues from client: " + pastDue.getCustomer__r().getName() + 
										"from mayor PastDue: " + pastDue.getName() + " / Total consolidated: " + pastDue.getTotal_Balance_Consolidate__c());
								
								// finds client past dues
								if (null != clientPastDuesMap.get(pastDue.getCustomer__c().getID())) {
									List<Past_Due__c> clientPastDues = clientPastDuesMap.get(pastDue.getCustomer__c().getID());
	
									// group client past dues that must be assigned - not previously assigned
									for (int j=0; j<clientPastDues.size(); j++) {
										if (!pastDuesAssignedMap.containsKey(clientPastDues.get(j).getId().getID())) pastDuesToAssign.add(clientPastDues.get(j));
									}
	
									// finds agents list which can attend the current past due (mayor amount and mayor category)
									if (null != agentCatProdMap )
										if (agentCatProdMap.containsKey(productId))
											if (agentCatProdMap.get(productId).containsKey(categoryId))
												agentList = agentCatProdMap.get(productId).get(categoryId);
									
									// breaks the agent search only if the list is over and if a agent was already found. 
									// This avoids the code from assigning client's past dues to diff agents. 
									for (int j=0; !agentFound && null != agentList && j<agentList.size(); j++) {
										Agent__c agent = agentList.get(j);
										logger.log(Level.INFO, "Trying to assign to agent: " + agent.getName());
										
										// Verifies cycle load was not reached by this agent
										int load = (agentLoadMap.containsKey(agent.getId().getID())) ? (agentLoadMap.get(agent.getId().getID()).size()) : 0;
										if (load < agent.getCPEE_Maximum_Cycle__c()) {
											
											// iterates over client past due list - the ones must be assigned
											for (int x=0; x<pastDuesToAssign.size(); x++) {
												Past_Due__c pastDueToAssign = pastDuesToAssign.get(x);
												logger.log(Level.INFO, "Testing agent: " + pastDueToAssign.getName());
												
												// find available date for this past due on agents days
												Date availableDate = getAvailableDate(agent, pastDueToAssign, currentCycle, holidayMap);
												if (null != availableDate) {
													agentFound = true;
													
													// 1. ASSIGN activity and past due
													CP_Activity__c activity = getNewActivity(pastDueToAssign, agent, availableDate); 
													newActivities.add(activity);
													assignPastDue(pastDueToAssign, activity, agent.getId(), productId, categoryId);
													logger.log(Level.INFO, "COLLECT - Assigned past due " + pastDueToAssign.getName() + 
														" / to agent '" + agent.getName() + "' " +
														pastDue.getCP_Product__r().getName() + " / " + pastDue.getCategory__r().getName());
												} else {
													// defines this past due to be assigned to a supervisor just in case had already found a 
													// agent which could attend the highest past due but, this agent did not have room for 
													// one more past due.
													if (agentFound) {
														pastDuesForSupervisors.put(pastDueToAssign.getId().getID(), pastDueToAssign);
														logger.log(Level.INFO, "Past due listed to be assigned to a supervisor: " +
															pastDueToAssign.getName());
													}
												}
	
											} // end past dues to assign for
	
										} else logger.log(Level.INFO, "COLLECT - Cycle load reached by agent: " + agent.getName());
									} // end agents for
									
									// if any past due could be assigned
									if (!agentFound) {
										for (int x=0; x<pastDuesToAssign.size(); x++) {
											Past_Due__c pastDueToAssign = pastDuesToAssign.get(x);
											pastDuesForSupervisors.put(pastDueToAssign.getId().getID(), pastDueToAssign);
											logger.log(Level.INFO, "Past due listed to be assigned to a supervisor: " +
												pastDueToAssign.getName());
										}
									}
									
								} else logger.log(Level.INFO, "COLLECT - Clients has no past dues");
							} else logger.log(Level.INFO, "COLLECT - Past Due was already assigned or defined to be assigned to supervisor" + pastDue.getName() + " / " +
							  		pastDue.getTotal_Balance_Consolidate__c()); 
						} catch(Exception e) { logger.log(Level.SEVERE, "COLLECT - Error " + pastDue.getName()); }					
					} // end for of past dues
					
					// assigns past dues to supervisors
					logger.log(Level.INFO, "COLLECT - Assign to supervisor: " + pastDuesForSupervisors.size());
					Iterator<Past_Due__c> ite = pastDuesForSupervisors.values().iterator();
					while (ite.hasNext()) {
						Past_Due__c pastDue = ite.next();
						if (null != categoryProductMap.get(pastDue.getCP_Product__c().getID()) &&
							null != categoryProductMap.get(pastDue.getCP_Product__c().getID()).get(pastDue.getCategory__c().getID())) {
							Category_Product__c categoryProduct = 
								(Category_Product__c) categoryProductMap.get(pastDue.getCP_Product__c().getID()).get(pastDue.getCategory__c().getID());
	
							// 1. ASSIGN new activity and past due
							CP_Activity__c activity = 
								getNewActivityForSupervisor(pastDue, categoryProduct.getSupervisor__r().getCPEE_Agent__c(),
										categoryProduct.getSupervisor__r().getName()); 
							newActivities.add(activity);
							assignPastDue(pastDue, activity, categoryProduct.getSupervisor__r().getCPEE_Agent__c(), 
									pastDue.getCP_Product__c().getID(), pastDue.getCategory__c().getID());
	
							logger.log(Level.INFO, "COLLECT - Assigned Past due " + pastDue.getName() + " / " + 
									pastDue.getTotal_Balance_Consolidate__c() + " to a supervisor " + 
									categoryProduct.getSupervisor__r().getName());
						} else logger.log(Level.INFO, "COLLECT - No Supervisor found for pastDue " + pastDue.getName());
					}
					
	
				} else logger.log(Level.SEVERE, "No Agent-category-product configuration found for this enterprise");			
			} else logger.log(Level.SEVERE, this.getClass().getPackage()+this.getClass().getName()+ "/ "+ "Required information was not received ");
			
			ClientSalesforce.update(pastDuesAssignedList);
			ClientSalesforce.create(newActivities);
		}
		return allRight;
	}

	/**
	 * Gets the next available date for a past due to be assigned to a agent
	 * @param agent Agent__c object with the information of the agent
	 * @return Date The date for the new activity. null if no available date is found.
	 */
	private Date getAvailableDate(Agent__c agent, Past_Due__c pastDue, Cycle__c cycle,Map<String, HashMap<Integer, Holiday__c>> holidayMap ) {
		Date assignedDate = new Date();
		boolean available = false;

		/*/ testing ------------ date created for testing - current date should be used instead
		Calendar miCal = Calendar.getInstance();				
		miCal.set(2010,0,29); //(int year, int month, int date)
		assignedDate = miCal.getTime();	
		// end testing ------------*/
		
		while (!available) {
			assignedDate = Utilities.getTimelessDate(assignedDate);
			//logger.log(Level.INFO, "COLLECT - Verifying availability of date: " + assignedDate.toString() + " for agent " + agent.getName() +
			//	" where dates to compare are: " + getTimelessDate(cycle.getCPEE_Cycle_Start_Date__c()).toString() + " and " +
			//	getTimelessDate(cycle.getCPEE_Cycle_End_Date__c()));
			
			// Verify the date belongs to the cycle	or not		
			if (// date is equal of after the start date. 
				(assignedDate.equals(Utilities.getTimelessDate(cycle.getCPEE_Cycle_Start_Date__c())) || 
				assignedDate.after(Utilities.getTimelessDate(cycle.getCPEE_Cycle_Start_Date__c()))) &&
				// and date is before end date
				assignedDate.before(Utilities.getTimelessDate(cycle.getCPEE_Cycle_End_Date__c()))) {

				boolean workingDay = isAgentWorkingDay(assignedDate, agent);
				boolean holyday = Utilities.isHoliday(assignedDate, holidayMap);

				if (null != assignedDate && workingDay && !holyday) {
					int cycleActivities = 0;
					
					// counts number of activities assigned to the agent for the date if there is any
					if (null != agentLoadMap.get(agent.getId().getID())) {
						List<CP_Activity__c> activities = agentLoadMap.get(agent.getId().getID());
						for (int i=0; i<activities.size(); i++) {
							CP_Activity__c activiy = activities.get(i);
							if (activiy.getScheduled_Date__c().getTime().compareTo(assignedDate) == 0) cycleActivities += 1;
						}// end for
					}

					// BINGO!! available date (no holyday, working day, agent load not reached 
					if (cycleActivities < agent.getMax_Daily_Load__c()) { 
						available = true; 
						//logger.log(Level.INFO, "COLLECT - Able to use this date.");
						break; 
					} //else logger.log(Level.INFO, "COLLECT - Agent daily load reached. Unable to use this date. " + 
					  //		cycleActivities + " / " + agent.getMax_Daily_Load__c());
					
				} //else logger.log(Level.INFO, "COLLECT - Skiped date since is not a working day, is a holiday or null.");
				
				// Sets the date to the day after
				assignedDate = Utilities.getNexCalendarDay(assignedDate);
				
			} else { 
				logger.log(Level.INFO, "COLLECT - Date out of cycle: " + assignedDate.toString()); 
				break;
			}
		}
		if (available) return assignedDate; else return null;
	}

	/**
	 * Verifies if the giving date is a available day for assignment, according with the configuration
	 * of the shift of the giving agent
	 * @param date Date must be evaluated
	 * @param agent Agent wants to be assigned
	 * @return true if day is a working day, false if not. 
	 */
	 private boolean isAgentWorkingDay(Date date, Agent__c agent) {
		 Shift__c shift = shiftMap.get(agent.getShift__c().getID());
		 Calendar myCal = Calendar.getInstance();

		 // verify this date day is a working day according to agent configuration
		 myCal.setTime(date);
		 switch (myCal.get(Calendar.DAY_OF_WEEK)) {
			 case 1: 
				//logger.log(Level.INFO, "COLLECT - Is sunday working day? " + shift.getSunday__c());
			 	return shift.getSunday__c(); 
			 case 2:  
				//logger.log(Level.INFO, "DAY_OF_WEEK - Is monday working day? " + shift.getMonday__c());
			 	return shift.getMonday__c();
			 case 3:  
				//logger.log(Level.INFO, "DAY_OF_WEEK - Is tuesday working day? " + shift.getTuesday__c());
			 	return shift.getTuesday__c();
			 case 4:  
				//logger.log(Level.INFO, "DAY_OF_WEEK - Is wednesday working day? " + shift.getWednesday__c());
			 	return shift.getWednesday__c();
			 case 5:  
				//logger.log(Level.INFO, "DAY_OF_WEEK - Is thursday working day? " + shift.getThursday__c());
			 	return shift.getThursday__c();
			 case 6:  
				//logger.log(Level.INFO, "DAY_OF_WEEK - Is friday working day? " + shift.getFriday__c());
			 	return shift.getFriday__c();
			 case 7:  
				//logger.log(Level.INFO, "DAY_OF_WEEK - Is saturday working day? " + shift.getSaturday__c());
			 	return shift.getSaturday__c();
			 default: break;
		 }
		 return false;		 
	 }

	/**
	 * Creates a map with the shifts. Latter will be used to assign the date of a activity assigned
	 * @param agentIds
	 * @param company
	 */
	private void getAgentShifts(List<String> agentIds, String company) {
		List<SObject> shifts = new ArrayList<SObject>();
		try {
			shifts = ClientSalesforce.query("Select s.Id, s.Wednesday__c, s.Tuesday__c, s.Thursday__c, s.Sunday__c, "+
				"s.Saturday__c, s.Name, s.Monday__c, s.Friday__c From Shift__c s where s.Company__c = '" + company + "'");
		} catch(Exception e) { logger.log(Level.INFO, "COLLECT - No shifts found "+e); }
		
		try {
			for (int i=0; i<shifts.size(); i++) {
				Shift__c shift = (Shift__c)shifts.get(i);
				if (!shiftMap.containsKey(shift.getId().getID())) shiftMap.put(shift.getId().getID(), shift); 
			}
		} catch(Exception e) {
			logger.log(Level.INFO, "COLLECT - Error searching for shifts "+e);
		}
	}

	/**
	 * Closes last period. 
	 * @param currentCycle
	 * @throws RemoteException
	 * @throws IllegalArgumentException
	 * @throws UnexpectedErrorFault
	 * @throws InvalidIdFault
	 * @throws LoginFault
	 * @throws Exception
	 */
	private void closeLastPeriod(Cycle__c currentCycle) throws RemoteException, IllegalArgumentException, UnexpectedErrorFault, InvalidIdFault, LoginFault, Exception {
		//logger.log(Level.INFO, "Current Cycle " + currentCycle.getPeriod__c());
		Cycle__c lastCycle = null;
		String period = null;
		String lastPeriod = null;
		
		// Gets current period
		period = currentCycle.getPeriod__c();

		int month = Integer.valueOf(period.substring(0, period.length()-4).toString());
		int year = Integer.valueOf(period.substring(period.length()-4, period.length()));		
		// Calculates last period:
		// validates go to the previous year if current month is January
		if (1 == month) { month = 12; year -= 1; } else month -= 1;
		String monthS = month<10 ? "0"+String.valueOf((month)) : String.valueOf((month));
		lastPeriod = monthS + String.valueOf(year);

		// finds last cycle
		try {
			lastCycle = (Cycle__c)ClientSalesforce.queryLimitOne(
				"Select Id, CPEE_Closed_Cycle__c from Cycle__c where Enterprise_Account__c = '" + currentCycle.getEnterprise_Account__c() + "' " +
				"and Period__c = '" + lastPeriod +"' LIMIT 1");
		} catch(Exception e) { logger.log(Level.INFO, "COLLECT - Previous cycle not found. " + lastPeriod +" / " + e); }
		if (null != lastCycle) {
			Cycle__c lastCycleTemp = new Cycle__c();
			lastCycleTemp.setId(lastCycle.getId()); 
			lastCycleTemp.setCPEE_Closed_Cycle__c(true);
			ClientSalesforce.update(lastCycleTemp);
		} //else { logger.log(Level.INFO, "Last Cycle not found"); }
	}

	/**
	 * Returns a map of the  enterprise account agents to be in charge of the assignment.
	 * @param currentCycle
	 * @return Map<String,HashMap<String,List<Agent__c>>> Organizes agents by products, categories. 
	 */
	private void constructAgentsMaps(Cycle__c currentCycle) {
		List<SObject> agentCategoryProductList = new ArrayList<SObject>();
		try {
			// a.Min_Daily_Load__c, a.Min_Cycle_Load__c, a.Max_Daily_Load__c, a.CPEE_Min_Cycle_Load__c
			agentCategoryProductList = ClientSalesforce.query(
				
				"Select Id, Categories_Products__r.CP_Product__c, Categories_Products__r.Category__c, Categories_Products__r.Id, "+
				"Categories_Products__c, CPEE_Agent__r.User__c, CPEE_Agent__r.Name,  CPEE_Agent__r.Min_Daily_Load__c, CPEE_Agent__r.Min_Cycle_Load__c, "+
				"CPEE_Agent__r.Max_Daily_Load__c, CPEE_Agent__r.CPEE_Maximum_Cycle__c, CPEE_Agent__r.Id, CPEE_Agent__c, a.Minimum_Amount_to_Allocate__c, " + 
				"a.Maximum_Amount_to_Allocate__c, a.CPEE_Agent__r.Shift__c From Agent_category_product__c a "+
				"where Categories_Products__r.Enterprise_Account__c = '"+ currentCycle.getEnterprise_Account__c() +"' and  "+
				"CPEE_Agent__r.Active__c = true order by CPEE_Agent__r.CPEE_Maximum_Cycle__c ASC");
		} catch(Exception e) { logger.log(Level.INFO, "COLLECT - No Agents-category-product found. " + e); }

		if (null != agentCategoryProductList && agentCategoryProductList.size()>0) {
			// Create Map<Product_Id,Map<Category_Id,Lista de Agent> 
			for (int i=0; i<agentCategoryProductList.size(); i++) {
				Agent_category_product__c agentCatProd = (Agent_category_product__c) agentCategoryProductList.get(i);
				String productId = agentCatProd.getCategories_Products__r().getCP_Product__c().getID();
				String categoryId = agentCatProd.getCategories_Products__r().getCategory__c().getID();
				Agent__c agent = agentCatProd.getCPEE_Agent__r();
				boolean found = false;
				
				// Creates list of agent ids
				agentIdsArray.add(agent.getId().getID());
				// Creates agents map
				// adds product map in case it does no exists
				if (!agentCatProdMap.containsKey(productId)) agentCatProdMap.put(productId, new HashMap<String,List<Agent__c>>());
				// adds category map in case it does not exist
				if (!agentCatProdMap.get(productId).containsKey(categoryId)) agentCatProdMap.get(productId).put(categoryId, new ArrayList<Agent__c>());
				// verifies if the agent was already added
				for (int j=0; j<agentCatProdMap.get(productId).get(categoryId).size(); j++) {
					if (agentCatProdMap.get(productId).get(categoryId).get(j).getId() == agent.getId()) found = true;
				}
				// in case agent was not found... adds it. 
				if (!found) {
					agentCatProdMap.get(productId).get(categoryId).add(agent);
				}
				
				// Creates agent-category-product map
				if (!categoryProductsMap.containsKey(agent.getId().getID())) 
					categoryProductsMap.put(agent.getId().getID(), new HashMap<String, HashMap<String,Agent_category_product__c>>());
				if (!categoryProductsMap.get(agent.getId().getID()).containsKey(productId))
					categoryProductsMap.get(agent.getId().getID()).put(productId, new HashMap<String,Agent_category_product__c>());
				if (!categoryProductsMap.get(agent.getId().getID()).get(productId).containsKey(categoryId))
					categoryProductsMap.get(agent.getId().getID()).get(productId).put(categoryId, agentCatProd);
			}
		}
	}
	
	/**
	 * Returns a map of past dues organized by client/product/category
	 * @param enterprise
	 * @param currentCycle
	 * @returnHashMap<String, HashMap<String, HashMap<String, ArrayList<Past_Due__c>>>>
	 */
	public void createPastDueSortedStructures(CP_Enterprise_Account__c enterprise,
		Cycle__c currentCycle) {
		// Create a List of past dues organized by clients
		List<SObject> pastDuesList = new ArrayList<SObject>();
 
		try {
			pastDuesList = ClientSalesforce.query(
				"Select p.Id, p.Name, p.Total_Balance_Local__c, p.Total_Balance_Foreign__c, p.Total_Balance_Consolidate__c, p.Past_Due_Amount_USD__c, "+
				"p.Past_Due_Amount_Local__c, p.Customer__r.Name, p.Customer__c, p.Category__r.To__c, p.Category__r.From__c, p.Company__c, p.Account_Info__c, "+
				"p.Category__r.Name, p.Category__c, p.CP_Product__r.Name, p.CP_Product__c, p.Enterprise_Account__c From Past_Due__c p  "+
				"where p.Cycle__c = '" + currentCycle.getId() + "' and Assigned__c = " + false + " and p.Status__c = '" + Constants.PAST_DUE_STATUS_CLOSED + "' and" +
				"p.Enterprise_Account__c = '" + enterprise.getId() + "' order by p.Total_Balance_Consolidate__c  DESC, p.Category__r.From__c DESC");
		} catch(Exception e) { logger.log(Level.INFO, "COLLECT - No PastDues found. " + e); }

		if (null != pastDuesList && pastDuesList.size() > 0) {
			for(int i=0; i<pastDuesList.size(); i++) {
				Past_Due__c pastDue = (Past_Due__c) pastDuesList.get(i);
				
				// Creates sorted list of past dues - for assignment by ranks
				pastDueSortedList.add(pastDue);
				
				// Creates client map - sorted list of past dues - for assignment by availability
				String customerId = pastDue.getCustomer__c().getID();
				if (!clientPastDuesMap.containsKey(customerId)) {
					clientPastDuesMap.put(customerId, new ArrayList<Past_Due__c>());
				}
				clientPastDuesMap.get(customerId).add(pastDue);
			}	
		}
	}
	
	/**
	 * Calculates agent current load
	 * @param currentCycle
	 */
	public void calculateAgentCurrentLoad(Cycle__c currentCycle, CP_Enterprise_Account__c enterprise) {
		List<SObject> activitiesList = new ArrayList<SObject>();
		
		// gets agent activities
		try {
			activitiesList = ClientSalesforce.query(
				"Select c.Id, c.Agent__c, c.Scheduled_Date__c From CP_Activity__c c where " +
				"c.Result_Code__c != '"+ enterprise.getCompany__r().getResult_Code_for_Closing_an_Activity__c() +"' and " +
				"c.Action_Code__c != '"+ enterprise.getCompany__r().getCode_for_Closing_an_Activity__c() +"' and " +
				"c.Company__c = '" + currentCycle.getCompany__c().getID() + "' and "+
				"c.Scheduled_Date__c > " + Utilities.getSalesforceDateTime(currentCycle.getCPEE_Cycle_Start_Date__c()) + " and " +
				"c.Scheduled_Date__c < " + Utilities.getSalesforceDateTime(currentCycle.getCPEE_Cycle_End_Date__c()) + " and "+ 
				"c.Agent__c in ", agentIdsArray, " order by c.Scheduled_Date__c");
		} catch(Exception e) { logger.log(Level.INFO, "COLLECT - No Agents current load found. " + e); }
		
		for (int i=0; activitiesList!= null && i<activitiesList.size(); i++ ) {
			CP_Activity__c activity = (CP_Activity__c) activitiesList.get(i);
			if(!agentLoadMap.containsKey(activity.getAgent__c())) agentLoadMap.put(activity.getAgent__c().getID(), new ArrayList<CP_Activity__c>());
			agentLoadMap.get(activity.getAgent__c().getID()).add(activity);
		}		
	}
	
	private void getCategoryProductConfiguration(String enterprise) {
		//categoryProductMap
		List<SObject> categoryProducts = new ArrayList<SObject>();
		try {
			categoryProducts = ClientSalesforce.query(
				"Select c.Supervisor__r.CPEE_Agent__c, c.Supervisor__r.SystemModstamp, c.Supervisor__r.LastModifiedById, "+
				"c.Supervisor__r.LastModifiedDate, c.Supervisor__r.CreatedById, c.Supervisor__r.CreatedDate, "+
				"c.Supervisor__r.CurrencyIsoCode, c.Supervisor__r.Name, c.Supervisor__r.IsDeleted, c.Supervisor__r.OwnerId, "+
				"c.Supervisor__r.Id, c.Supervisor__c, c.Category__c, c.CP_Product__c From Category_Product__c c where " +
				"c.Enterprise_Account__c = '" + enterprise + "'");
		} catch(Exception e) { logger.log(Level.INFO, "COLLECT - No Agents current load found. " + e); }

		for (int i=0; i<categoryProducts.size(); i++) {
			Category_Product__c categoryProduct = (Category_Product__c) categoryProducts.get(i);
			String product = categoryProduct.getCP_Product__c().getID();
			String category = categoryProduct.getCategory__c().getID();
			if (!categoryProductMap.containsKey(product)) 
				categoryProductMap.put(product, new HashMap<String, Category_Product__c>());
			if (!categoryProductMap.get(product).containsKey(category))
				categoryProductMap.get(product).put(category, categoryProduct);
		}
	}
	
	/**
	 * Creates a new activity from the giving past due
	 * @param pastDue
	 * @param agent
	 * @param availableDate
	 * @return Activity fulfilled with the required information
	 */
	private CP_Activity__c getNewActivity(Past_Due__c pastDue, Agent__c agent, Date availableDate) {
		CP_Activity__c activity = new CP_Activity__c();
		activity.setCompany__c(pastDue.getCompany__c());
		activity.setEnterprise_Account__c(pastDue.getEnterprise_Account__c());
		activity.setAccount_Info__c(pastDue.getAccount_Info__c());
		activity.setCustomer__c(pastDue.getCustomer__c());

		activity.setPast_Due__c(pastDue.getId());
		activity.setAgent__c(agent.getId());
		activity.setType__c("Call");
		activity.setName(agent.getName() + "-" + pastDue.getName());
		
		Calendar dateCal = Calendar.getInstance();
		dateCal.setTime(availableDate);
		activity.setScheduled_Date__c(dateCal);
		
		//logger.log(Level.INFO, "New activity: " + activity.getName() + " / " +
		//	activity.getCompany__c() +" / "+ activity.getEnterprise_Account__c() +" / "+
		//	activity.getPast_Due__c() +" / "+ activity.getAgent__c()  + " / " + activity.getScheduled_Date__c().getTime().toString());
		return activity;
	}
	
	/**
	 * Creates a new activity from the giving past due
	 * @param pastDue
	 * @param agentId agent id
	 * @param name agent name
	 * @return
	 */
	private CP_Activity__c getNewActivityForSupervisor(Past_Due__c pastDue, ID agentId, String name) {
		CP_Activity__c activity = new CP_Activity__c();
		activity.setCompany__c(pastDue.getCompany__c());
		activity.setEnterprise_Account__c(pastDue.getEnterprise_Account__c());
		activity.setAccount_Info__c(pastDue.getAccount_Info__c());
		activity.setCustomer__c(pastDue.getCustomer__c());
		
		activity.setPast_Due__c(pastDue.getId());
		activity.setAgent__c(agentId);
		activity.setType__c("Call");
		activity.setName(name + "-" + pastDue.getName());
		
		//logger.log(Level.INFO, "New activity: " + activity.getName() + " / " +
		//	activity.getCompany__c() +" / "+ activity.getEnterprise_Account__c() +" / "+
		//	activity.getPast_Due__c() +" / "+ activity.getAgent__c());
		return activity;
	}
	
	/**
	 * Assign a past due to the giving agent. Handle Agent information update like load. 
	 * @param pastDue Past due to be assign
	 * @param activity activity of the past due
	 * @param agent agent the past due will be assigned to
	 * @param productId Identifier of the product assigned
	 * @param categoryId Identifier of the category assigned
	 */
	private void assignPastDue(Past_Due__c pastDue, CP_Activity__c activity, ID agentId, String productId, String categoryId) {
		try {
			pastDue.setAgent__c(agentId);
			//pastDue.setOwner(agent.getUser__c());
			// adds past due to a map where all assigned past dues will be
			pastDuesAssignedMap.put(pastDue.getId().getID(), pastDue);
			
			Past_Due__c newPastDue = new Past_Due__c();
			newPastDue.setId(pastDue.getId());
			newPastDue.setAgent__c(agentId);
			newPastDue.setAssigned__c(true);
			pastDuesAssignedList.add(newPastDue);
			 
			// Adds activities to agent's load map
			if (!agentLoadMap.containsKey(agentId.getID())) agentLoadMap.put(agentId.getID(), new ArrayList<CP_Activity__c>()); 
			agentLoadMap.get(agentId.getID()).add(activity);

		} catch(Exception e) { 
			logger.log(Level.SEVERE, "COLLECT - ERROR al asignar past due " + pastDue +" / "+ activity  +" / "+ agentId
				+" / "+ productId +" / "+categoryId); 
		}
	}
}
